package cn.com.lyj6851.auth.config;


import java.util.Arrays;
import java.util.concurrent.TimeUnit;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import cn.com.lyj6851.auth.config.jwt.CustomTokenEnhancer;
import cn.com.lyj6851.auth.exception.WebResponseExceptionTranslator;
import cn.com.lyj6851.auth.utils.MyRedisTokenStore;
import cn.com.lyj6851.common.properties.SecurityConstants;
import lombok.extern.slf4j.Slf4j;

/**
spring.security.oauth2.client.authorization-uri=/oauth/authorize
spring.security.oauth2.client.token-uri=/oauth/token

	/oauth/authorize(授权端，授权码模式使用)
	/oauth/token(令牌端，获取 token)
	/oauth/check_token(资源服务器用来校验token)
	/oauth/confirm_access(用户发送确认授权)
	/oauth/error(认证失败)
	/oauth/token_key(如果使用JWT，可以获的公钥用于 token 的验签)

ClientDetailsServiceConfigurer：用来配置客户端详情信息，一般使用数据库来存储或读取应用配置的详情信息（client_id ，client_secret，redirect_uri 等配置信息）。
AuthorizationServerSecurityConfigurer：用来配置令牌端点(Token Endpoint)的安全与权限访问。
AuthorizationServerEndpointsConfigurer：用来配置授权以及令牌（Token）的访问端点和令牌服务（比如：配置令牌的签名与存储方式）
 
*/
@Slf4j
@Configuration
@EnableAuthorizationServer
public class Oauth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter{

    //RSA配置
    @Value("${config.oauth2.privateKey}")
    private String privateKey;
    @Value("${config.oauth2.publicKey}")
    private String publicKey;
    
    //认证管理(需要引入,要不然authorized_grant_type没有password模式)
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Autowired
	private DataSource dataSource;

	@Autowired
	private TokenStore tokenStore;
	
	@Autowired
    private RedisConnectionFactory redisConnectionFactory;
	
	@Autowired
    private UserDetailsService userDetailsService;
	
	@Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;
	
	@Autowired
    private WebResponseExceptionTranslator webResponseExceptionTranslator;

	
	/**
	 * 指定token存储的位置
	 *
	 * @return
	 */
	@Bean
    @ConditionalOnProperty(prefix = "micro.service.oauth2", name = "storeType", havingValue = "redis")
    public TokenStore redisTokenStore(){
        return new MyRedisTokenStore(redisConnectionFactory);
    }
    // matchIfMissing ：当tokenStore没有值的时候是否生效
    // 当tokenStore = jwt的时候或则tokenStore没有配置的时候使用下面的配置; 也就是默认使用jwt
    @Bean
    @ConditionalOnProperty(prefix = "micro.service.oauth2", name = "storeType", havingValue = "jwt", matchIfMissing = true)
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter);
    }
    
    
	/**
	 * 1.从数据库获取并配置给哪些应用发令牌
	 */
	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetails());
	}
	
	@Bean
    public ClientDetailsService clientDetails() {
		// 自定义数据库表
		JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        clientDetailsService.setSelectClientDetailsSql(SecurityConstants.DEFAULT_SELECT_STATEMENT);
        clientDetailsService.setFindClientDetailsSql(SecurityConstants.DEFAULT_FIND_STATEMENT);
        return clientDetailsService;
    }
	/**
	 * 客户端来向认证服务器获取签名的时候需要登录认证身份才能获取
	 * 因为客户端需要用密钥解密jwt字符串  NoOpPasswordEncoder.getInstance()
	 * security.passwordEncoder(NoOpPasswordEncoder.getInstance()); // 密码加密策略
	 */
	@Override
	public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
		security.tokenKeyAccess("permitAll()")         //url:/oauth/token_key,exposes public key for token verification if using JWT tokens
		        .checkTokenAccess("isAuthenticated()") //url:/oauth/check_token allow check token
		        //.passwordEncoder(NoOpPasswordEncoder.getInstance())
		        
		        .allowFormAuthenticationForClients();
	}
   
	/**
	 * 认证令牌的入口点
	 * 配置使用jwt生成令牌
	 * 根据用户配置的选项,确定使用redis还是使用本地存储token
	 */
	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
		//指定token存储位置
		endpoints.userDetailsService(userDetailsService).tokenStore(tokenStore).authenticationManager(authenticationManager);
		// 自定义token生成方式
		TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
		tokenEnhancerChain.setTokenEnhancers(Arrays.asList(customerEnhancer(), jwtAccessTokenConverter));
		endpoints.tokenEnhancer(tokenEnhancerChain);
		
		// 配置TokenServices参数
		DefaultTokenServices tokenServices = (DefaultTokenServices) endpoints.getDefaultAuthorizationServerTokenServices();
		tokenServices.setTokenStore(endpoints.getTokenStore());
		tokenServices.setSupportRefreshToken(false);
		tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
		tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
		tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1)); // 1天
		tokenServices.setRefreshTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(7)); //7天
		
		endpoints.tokenServices(tokenServices)
				 .exceptionTranslator(webResponseExceptionTranslator);// 无效自定义异常
		
		super.configure(endpoints);
	}
	/**
	 * 注入自定义token生成方式
	 *
	 * @return
	 */
	@Bean
	public TokenEnhancer customerEnhancer() {
		return new CustomTokenEnhancer();
	}
	/**
     * 定义token 签名的方式(非对称加密算法来对 Token 进行签名,也可以使用对称加密方式)
     *
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        log.info("Initializing JWT with public key:\n" + publicKey);
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        //对称加密方式
        //converter.setSigningKey("micosrv_signing_key");
        //非对称加密方式
        converter.setSigningKey(privateKey);
        converter.setVerifierKey(publicKey);
        return converter;
    }
	
	
}
